package cookieJar

import (
	"bufio"
	"bytes"
	"encoding/json"
	"gitee.com/kaylee595/gkaylee"
	"github.com/kaylee595/tracerr"
	"github.com/samber/lo"
	"io"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
)

type Jar struct {
	j *cookiejar.Jar
}

func NewJar() *Jar {
	return &Jar{j: gkaylee.PanicIfError(cookiejar.New(nil))}
}

func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
	j.j.SetCookies(u, cookies)
}

func (j *Jar) Cookies(u *url.URL) []*http.Cookie {
	return j.j.Cookies(u)
}

// Cookie CopyFrom http.Cookie
type Cookie struct {
	Name  string
	Value string

	Path       string `json:",omitempty"` // optional
	Domain     string `json:",omitempty"` // optional
	RawExpires string `json:",omitempty"` // for reading cookies only

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge   int           `json:",omitempty"`
	Secure   bool          `json:",omitempty"`
	HttpOnly bool          `json:",omitempty"`
	SameSite http.SameSite `json:",omitempty"`
	Raw      string        `json:",omitempty"`
	Unparsed []string      `json:",omitempty"` // Raw text of unparsed attribute-value pairs
}

func (j *Jar) SaveToFile(u *url.URL, filename string, saveBefore ...func(*Cookie)) error {
	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
	if err != nil {
		return tracerr.Wrap(err)
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	defer writer.Flush()

	cookies_ := j.Cookies(u)
	cookies := lo.Map(cookies_, func(item *http.Cookie, index int) *Cookie {
		return &Cookie{
			Name:  item.Name,
			Value: item.Value,
		}
	})
	for _, cookie := range cookies {
		// 获取到的cookie除了key和value被导出, 其他字段全部没有被导出.
		// 如果后续将只有key和value的对象重新set回去时, 会缺省domain的内容
		// 缺省domain将导致cookiejar在后续重新获取cookie内容时, 匹配域名使用的是HostOnly
		// 在HostOnly模式下, 你无法通过二级域名去获取到一级域名下的cookie
		// 所以这里如果你不为cookie设置domain, 那么后续匹配cookie时必须使用和u.Host的域名,
		// 假设u.Host域名为125.la, 后续请求的是bbs.125.la, 由于内部hostOnly匹配规则影响, 你将使用不到该cookie.
		cookie.Domain = u.Host
		if len(saveBefore) > 0 {
			saveBefore[0](cookie)
		}
	}

	if len(cookies) > 0 {
		jsonEncoder := json.NewEncoder(writer)
		jsonEncoder.SetIndent("", "\t")
		err = jsonEncoder.Encode(cookies)
		if err != nil {
			return tracerr.Wrap(err)
		}
	}
	return nil
}

func (j *Jar) LoadFromFile(u *url.URL, filename string, useBefore ...func(*http.Cookie)) error {
	file, err := os.OpenFile(filename, os.O_RDWR, 0660)
	if err != nil {
		return tracerr.Wrap(err)
	}
	defer file.Close()
	reader := bufio.NewReader(file)
	var cookies []*http.Cookie
	err = json.NewDecoder(reader).Decode(&cookies)
	if err != nil {
		_, _ = file.Seek(0, io.SeekStart)
		reader.Reset(file)
		var tempErr error
		cookies, tempErr = fromApplication(reader)
		if tempErr != nil {
			_, _ = file.Seek(0, io.SeekStart)
			reader.Reset(file)
			cookies, tempErr = fromHTTP(reader)
			if tempErr != nil {
				return tracerr.Wrap(err)
			}
		}
	}
	for _, cookie := range cookies {
		if len(cookie.Domain) == 0 {
			// 缺省domain将导致cookiejar在后续重新获取cookie内容时, 匹配域名使用的是HostOnly
			// 在HostOnly模式下, 你无法通过二级域名去获取到一级域名下的cookie
			// 所以这里如果你不为cookie设置domain, 那么后续匹配cookie时必须使用和u.Host的域名,
			// 假设u.Host域名为125.la, 后续请求的是bbs.125.la, 由于内部hostOnly匹配规则影响, 你将使用不到该cookie.
			cookie.Domain = u.Host
		}
		if len(useBefore) > 0 {
			useBefore[0](cookie)
		}
	}
	if len(cookies) > 0 {
		j.SetCookies(u, cookies)
	}
	return nil
}

func fromApplication(reader io.Reader) ([]*http.Cookie, error) {
	scanner := bufio.NewScanner(reader)
	cookies := make([]*http.Cookie, 0, 20)
	for scanner.Scan() {
		text := scanner.Text()
		if !strings.ContainsRune(text, '	') {
			return nil, tracerr.New("不受支持的格式")
		}
		fields := strings.Fields(text)
		if len(fields) < 2 {
			continue
		}
		name, val := fields[0], fields[1]
		cookies = append(cookies, &http.Cookie{
			Name:  name,
			Value: val,
		})
	}
	return cookies, scanner.Err()
}

func fromHTTP(reader io.Reader) ([]*http.Cookie, error) {
	all, err := io.ReadAll(reader)
	if err != nil {
		return nil, tracerr.Wrap(err)
	}
	if !bytes.Contains(all, []byte("; ")) {
		return nil, tracerr.New("不受支持的格式")
	}
	cookies := make([]*http.Cookie, 0, 20)
	for _, item := range bytes.Split(all, []byte("; ")) {
		cookie := string(item)
		name, val, found := strings.Cut(cookie, "=")
		if !found {
			continue
		}
		cookies = append(cookies, &http.Cookie{
			Name:  name,
			Value: val,
		})
	}
	if len(cookies) == 0 {
		return nil, tracerr.New("不受支持的格式")
	}
	return cookies, nil
}
